#include <iostream>
#include <fstream>
#include <cstring>
#include <cassert>

#include "endian.h"
#include "common.h"
#include "dumpfile.h"

using namespace std;

/*Pointers in the dump relate to the same address space as the nSG_Address below*/
struct ScatGather_t
{
    Iu32        nSG_Address;
    Iu32        nSG_Length;
};
 
/* ISS_ADAPTER_DUMP is the initialiser for IdString[16] below */
#define ISS_ADAPTER_DUMP "ISS_ADAPTER_DUMP"

struct DumpHeaderInfo_t
{
    Iu8          IdString[16];  /* "ISS_ADAPTER_DUMP" */
    Iu8          nVersion;      /* =1, though I, RGC, don't think anyone cares */
    Iu8          nArchitecture; /* AA_750 for Arrowhead (ipntra.h) */
    Iu16         nSequenceNo;   /* Increments per Adapter for each dump taken */ 
    WWName       nAdapId;       /* cfmgr/wwname.h for f/w.  ?? for host */
    Iu32         nEpochSeconds;
    Iu32         nMillisecs;
    Iu32         nSectorCount;
    Ibool        CapturedByHost; /* This field was BlockSize - which is not required*/
    Iu32         nScatCount;
    ScatGather_t ScatList[(512-48)/sizeof(ScatGather_t) ]; /*Fill to 512 bytes*/ 
};

static DumpHeaderInfo_t * pDumpHeader = NULL;
static size_t szDumpSize = 0;

static enum endianness Dmp_PpcEndian(Iu32 ppc)
{
    enum endianness ret = BIG;
    if( ppc >= 0xf0000000) ret = LITTLE;
    if( ppc >= 0x800d0000 && ppc <= 0x800d3fff )
        ret = LITTLE;
    return ret;
}

// returns 0 if success
// -1 if not found
// -2 if format wrong
int Dmp_Load(char* path)
{
    ifstream fdump(path, ios_base::in | ios_base::binary);
    if( ! fdump.is_open() )
        return -1;

    fdump.seekg(0, ios_base::end);
    szDumpSize = fdump.tellg();

    if( pDumpHeader != NULL )
        delete [] pDumpHeader;
    pDumpHeader = (DumpHeaderInfo_t *)new char[szDumpSize];

    fdump.seekg(0, ios_base::beg);
    fdump.read((char*)pDumpHeader, szDumpSize);

    if( szDumpSize < 16 )
        return -2;
    if( strncmp((char*)pDumpHeader, ISS_ADAPTER_DUMP, 16 ) != 0 )
        return -2;

    return 0;
}

void * Dmp_OffsetToBuffer(Iu32 offset)
{
    return ((char*)pDumpHeader+offset);
}

Iu32 Dmp_Iu32AtOffset(Iu32 offset, Iu32 endian )
{
    Iu32 ret = *(Iu32*)Dmp_OffsetToBuffer(offset);
    if( endian != ENDIANNESS )
        ret = NET_ULONG(ret);
    return ret;
}

Iu16 Dmp_Iu16AtOffset(Iu32 offset, Iu32 endian)
{
    Iu16 ret = *(Iu16*)Dmp_OffsetToBuffer(offset);
    if( endian != ENDIANNESS )
        ret = NET_USHORT(ret);
    return ret;
}

Iu8 Dmp_Iu8AtOffset(Iu32 offset)
{
    Iu8 ret = *(Iu8*)Dmp_OffsetToBuffer(offset);
    return ret;
}

Iu32 Dmp_Iu32AtPpc(Iu32 ppc)
{
    Iu32 offset = Dmp_PpcToOffset(ppc);
    return Dmp_Iu32AtOffset(offset, Dmp_PpcEndian(ppc));
}

Iu16 Dmp_Iu16AtPpc(Iu32 ppc)
{
    Iu32 offset = Dmp_PpcToOffset(ppc);
    return Dmp_Iu16AtOffset(offset, Dmp_PpcEndian(ppc));
}

Iu8 Dmp_Iu8AtPpc(Iu32 ppc)
{
    Iu32 offset = Dmp_PpcToOffset(ppc);
    return Dmp_Iu8AtOffset(offset);
}

Iu32 Dmp_SGE_Address(Iu32 index)
{
    assert( index < pDumpHeader->nScatCount );

    const ScatGather_t& sg = pDumpHeader->ScatList[index];
    return sg.nSG_Address;
}

Iu32 Dmp_SGE_Length(Iu32 index)
{
    assert( index < pDumpHeader->nScatCount );

    const ScatGather_t& sg = pDumpHeader->ScatList[index];
    return sg.nSG_Length;
}

Iu32 Dmp_SG_Length()
{
    return pDumpHeader->nScatCount;
}

void Dmp_PrintHeader()
{
    for( Iu32 i = 0; i < pDumpHeader->nScatCount; i++ ) {
        const ScatGather_t& sg = pDumpHeader->ScatList[i];
        printf("0x%08lx - 0x%08lx\n", sg.nSG_Address, sg.nSG_Length);
    }
}

Iu32 Dmp_OffsetToPpc(Iu32 offset)
{
    if( offset < 512 ) return 0;

    Iu32 ppc = 0;
    Iu32 i;
    offset -= 512;
    for( i = 0; i < pDumpHeader->nScatCount; i++ ) {
        const ScatGather_t& sg = pDumpHeader->ScatList[i];
        if( offset < sg.nSG_Length ) {
            ppc = sg.nSG_Address + offset;
            break;
        }
        offset -= sg.nSG_Length;
    }

    if( i >= pDumpHeader->nScatCount )
        return 0;
    return ppc;
}

Iu32 Dmp_PpcToOffset(Iu32 ppc)
{
    Iu32 offset = 512;
    Iu32 i;
    for( i = 0; i < pDumpHeader->nScatCount; i++ ) {
        const ScatGather_t& sg = pDumpHeader->ScatList[i];
        if( ppc >= sg.nSG_Address &&
            ppc < sg.nSG_Address + sg.nSG_Length) {
            offset += ppc - sg.nSG_Address;
            break;
        }
        offset += sg.nSG_Length;
    }
    if( i >= pDumpHeader->nScatCount )
        offset = 0;
    return offset;
}

Iu32 Dmp_Size()
{
    return szDumpSize;
}

Is32 Dmp_Iu32Search(Iu32 entry, Iu32 start)
{
    Iu32 i;
    for( i = start; i <= szDumpSize - 4; i+=4 ) {
        Iu32 value = *(Iu32*)((char*)pDumpHeader+i);
        if(value == entry || NET_ULONG(value) == entry)
            return (Is32)i;
    }
    return -1;
}
